package com.mdl.common.util.bean;

import javassist.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

public class JavassistBeanCopierClassCreater<T, E> {

	private static final Log logger = LogFactory.getLog(JavassistBeanCopierClassCreater.class);

	private Class<?> mapperClass;

	private Class<T> fromClazz;

	private Class<E> toClazz;

	private boolean checkIsSet;

	JavassistBeanCopierClassCreater(Class<T> fromClazz, Class<E> toClazz, boolean checkIsSet) {
		this.fromClazz = fromClazz;
		this.toClazz = toClazz;
		this.checkIsSet = checkIsSet;
		String className = this.createClassName();
		try {
			ClassPool pool = JavassistUtil.getClassPool();
			CtClass beanCopierClass = pool.get(BeanCopier.class.getName());
			ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
			try {
				pool.getCtClass(className);
				// 如果已经有同名类就赋值
				this.mapperClass = classLoader.loadClass(className);
			} catch (NotFoundException e) {
				// 没有找到，就创建新的class
				CtClass cc = pool.makeClass(className);
				cc.setInterfaces(new CtClass[] { beanCopierClass });
				String codeSrc = this.createMethodSrc();
				if (logger.isDebugEnabled()) {
					logger.debug("[" + className + "] code:\n" + codeSrc);
				}
				this.createMethod(cc, codeSrc);
				this.mapperClass = cc.toClass(classLoader, classLoader.getClass().getProtectionDomain());
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private void createMethod(CtClass cc, String src) {
		try {
			CtMethod method = CtNewMethod.make(src, cc);
			cc.addMethod(method);
		} catch (CannotCompileException e) {
			throw new RuntimeException("source code:\n" + src, e);
		}
	}

	Class<?> getMapperClass() {
		return mapperClass;
	}

	/**
	 * 生成类名称
	 */
	private String createClassName() {
		int idx = this.fromClazz.getName().lastIndexOf(".");
		int idx0 = toClazz.getName().lastIndexOf(".");
		String shortFromName = this.fromClazz.getName().substring(idx + 1);
		String shortToName = toClazz.getName().substring(idx0 + 1);
		String pkgName = this.fromClazz.getName().substring(0, idx);
		StringBuilder sb = new StringBuilder();
		sb.append(pkgName).append('.').append(shortFromName).append('_');
		sb.append(shortToName);
		if (this.checkIsSet) {
			sb.append("_CheckIsSet_");
		}
		sb.append("Javassist$BeanCopier");
		return sb.toString();
	}

	private String createMethodSrc() {
		StringBuilder sb = new StringBuilder("public void copy(Object from, Object to){\n");
		String fromClassName = this.fromClazz.getName();
		String toClazzName = toClazz.getName();
		sb.append(fromClassName).append(" _from = (").append(fromClassName).append(")from;\n");
		sb.append(toClazzName).append(" _to=(").append(toClazzName).append(")to;\n");
		Field[] fields = toClazz.getDeclaredFields();
		// 进行 obj.setter(from.getter....)等赋值操作
		for (Field toField : fields) {
			Field fromField;
			String name = toField.getName();
			String isSetterMethodName = this.getIsSetterMethodName(name);
			boolean hasIsSetterMethod = this.hasIsSetMethod(fromClazz, isSetterMethodName);
			try {
				fromField = this.fromClazz.getDeclaredField(name);
			} catch (NoSuchFieldException e) {
				//检测是否有对应的getter
				String methodName = this.createGetterMethodName(toField);
				if (this.hasGetterMethod(fromClazz, methodName)) {
					String getter = this.createGetterMethodName(toField);
					String setter = createSetterOrGetterMethodString("set", name);
					if (this.checkIsSet && hasIsSetterMethod) {
						sb.append("if(_from.").append(isSetterMethodName).append("()) {\n");
					}
					sb.append("_to.").append(setter).append("(_from.").append(getter).append("());\n");
					if (this.checkIsSet && hasIsSetterMethod) {
						sb.append("}\n");
					}
				}
				continue;
			}
			if (this.checkIsSet && hasIsSetterMethod) {
				sb.append("if(_from.").append(isSetterMethodName).append("()) {\n");
			}
			this.processFromField(toField, fromField, toClazzName, fromClassName, sb);
			if (this.checkIsSet && hasIsSetterMethod) {
				sb.append("}\n");
			}
		}
		sb.append("}");
		return sb.toString();
	}

	private void processFromField(Field toField, Field fromField, String toClazzName, String fromClassName,
			StringBuilder sb) {
		String name = toField.getName();
		Class fromFieldType = fromField.getType();
		Class toFieldType = toField.getType();
		boolean exchangeEnum = false;
		if (!fromFieldType.equals(toFieldType) && (fromFieldType.isEnum() && toFieldType.isEnum())) {
			//都是枚举，但是是不同类型的枚举
			exchangeEnum = true;
		}
		if (exchangeEnum) {
			boolean hasFindByValueInToClazz = this.hasFindByValueInEnum(toFieldType);
			if (hasFindByValueInToClazz) {
				String getter = createSetterOrGetterMethodString("get", name);
				String setter = createSetterOrGetterMethodString("set", name);
				if (!this.hasSetterMethod(toClazz, setter, toField) || !this.hasGetterMethod(toClazz, getter)) {
					return;
				}
				sb.append("if(_from.").append(getter).append("()!=null){\n");
				sb.append("_to.").append(setter).append("(").append(toFieldType.getName()).append(".findByValue(_from.")
						.append(getter).append("().getValue()));\n");
				sb.append("}\n");
			}
		} else {
			if (fromFieldType.isEnum() && !toFieldType.isEnum()) {
				this.checkEnumConvert(toClazzName, name, toFieldType);
				String getter = createSetterOrGetterMethodString("get", name);
				String setter = createSetterOrGetterMethodString("set", name);
				//双方不是同一个类型，一个是枚举，一个不是，那么就进行到to的类型数据转换，只支持int类型的enum
				sb.append("if(_from.").append(getter).append("()==null){\n");
				if (toFieldType.equals(byte.class)) {
					//byte
					sb.append("_to.").append(setter).append("(").append(MethodUtil.class.getName())
							.append(".int2Byte(0));\n");
				} else if (toFieldType.equals(short.class)) {
					//short
					sb.append("_to.").append(setter).append("(").append(MethodUtil.class.getName())
							.append(".int2Short(0));\n");
				} else {
					if (toFieldType.equals(int.class)) {
						//int
						sb.append("_to.").append(setter).append("(0);\n");
					} else {
						//integer
						sb.append("_to.").append(setter).append("(null);\n");
					}

				}
				sb.append("}\n");
				sb.append("else{\n");
				if (toFieldType.equals(byte.class)) {
					//byte
					sb.append("_to.").append(setter).append("(").append(MethodUtil.class.getName())
							.append(".int2Byte(_from.").append(getter).append("().getValue()));\n");
				} else if (toFieldType.equals(short.class)) {
					//short
					sb.append("_to.").append(setter).append("(").append(MethodUtil.class.getName())
							.append(".int2Short(_from.").append(getter).append("().getValue()));\n");
				} else {
					if (toFieldType.equals(int.class)) {
						//int
						sb.append("_to.").append(setter).append("(_from.").append(getter).append("().getValue());\n");
					} else {
						//integer
						sb.append("_to.").append(setter).append("(new Integer(_from.").append(getter).append("().getValue()));\n");
					}
				}
				sb.append("}\n");
			} else if (!fromFieldType.isEnum() && toFieldType.isEnum()) {
				this.checkEnumConvert(fromClassName, name, fromFieldType);
				String getter = createSetterOrGetterMethodString("get", name);
				String setter = createSetterOrGetterMethodString("set", name);

				// 双方不是同一个类型，一个是枚举，一个不是，那么就进行到to的类型数据转换，
				if (fromFieldType.equals(Integer.class)) {
					sb.append("_to.").append(setter).append("(").append(toFieldType.getName())
							.append(".findByValue(_from.").append(getter).append("().intValue()));\n");
				} else if (fromFieldType.equals(Short.class)) {
					sb.append("_to.").append(setter).append("(").append(toFieldType.getName())
							.append(".findByValue(_from.").append(getter).append("().shortValue()));\n");
				} else if (fromFieldType.equals(Byte.class)) {
					sb.append("_to.").append(setter).append("(").append(toFieldType.getName())
							.append(".findByValue(_from.").append(getter).append("().byteValue()));\n");
				} else {
					sb.append("_to.").append(setter).append("(").append(toFieldType.getName())
							.append(".findByValue(_from.").append(getter).append("()));\n");
				}
			} else {
				String getter;
				if (MethodUtil.isBoxedBooleanFieldType(fromFieldType)) {
					getter = this.createSetterOrGetterMethodString("get", name);
				} else {
					getter = this.createGetterMethodName(fromField);
				}
				String setter = createSetterOrGetterMethodString("set", name);
				if (!this.hasSetterMethod(toClazz, setter, toField) || !this.hasGetterMethod(fromClazz, getter)) {
					return;
				}
				if (fromFieldType.equals(List.class)) {
					if (isSameParameterizedType4List(fromField, this.toClazz, setter)) {
						sb.append("if(_from.").append(getter).append("()!=null){\n");
						sb.append("_to.").append(setter).append("(new java.util.ArrayList(_from.").append(getter)
								.append("()));\n");
						sb.append("}\n");
					}
				} else if (fromFieldType.equals(Map.class)) {
					if (isSameParameterizedType4Map(fromField, this.toClazz, setter)) {
						sb.append("if(_from.").append(getter).append("()!=null){\n");
						sb.append("_to.").append(setter).append("(new java.util.HashMap(_from.").append(getter)
								.append("()));\n");
						sb.append("}\n");
					}
				} else {
					if (MethodUtil.isStringType(toFieldType)) {
						//_to.setCode(_from.getCode());
						sb.append("_to.").append(setter).append("(_from.").append(getter).append("());\n");
					} else if (MethodUtil.isOrigin(fromFieldType) && MethodUtil.isBoxed(toFieldType)) {
						//_to.setCode(new java.lang.Integer(_from.getCode()));
						//_to.setCode(Integer.valueOf(_from.getCode()));
						sb.append("_to.").append(setter).append("(").append(MethodUtil.getBoxedSubCode(toFieldType))
								.append("(").append("_from.").append(getter).append("()").append(")").append(");\n");
					} else if (MethodUtil.isOrigin(toFieldType) && MethodUtil.isBoxed(fromFieldType)) {
						//_to.setCode(_from.getCode().intValue());
						sb.append("if(").append("_from.").append(getter).append("()!=null){\n");
						sb.append("_to.").append(setter).append("(_from.").append(getter).append("().")
								.append(MethodUtil.getUnBoxedSubCode(fromFieldType)).append(");\n");
						sb.append("}\n");
					} else {
						//_to.setCode(_from.getCode());
						sb.append("_to.").append(setter).append("(_from.").append(getter).append("());\n");
					}
				}
			}
		}
	}

	private static boolean isSameParameterizedType4List(Field fromField, Class toClazz, String setter) {
		try {
			Type fromGenericType = fromField.getGenericType();
			Method method = toClazz.getMethod(setter, List.class);
			Type[] types = method.getGenericParameterTypes();
			if (types == null || types.length == 0) {
				return false;
			}
			ParameterizedType fromPt = (ParameterizedType) fromGenericType;
			ParameterizedType toPt = (ParameterizedType) types[0];
			Type[] fromPtTypes = fromPt.getActualTypeArguments();
			Type[] toPtTypes = toPt.getActualTypeArguments();
			return fromPtTypes != null && toPtTypes != null && fromPtTypes.length == toPtTypes.length
					&& fromPtTypes[0].equals(toPtTypes[0]);
		} catch (NoSuchMethodException e) {
			return false;
		}
	}

	private static boolean isSameParameterizedType4Map(Field fromField, Class toClazz, String setter) {
		try {
			Type fromGenericType = fromField.getGenericType();
			Method method = toClazz.getMethod(setter, Map.class);
			Type[] types = method.getGenericParameterTypes();
			if (types == null || types.length == 0) {
				return false;
			}
			ParameterizedType fromPt = (ParameterizedType) fromGenericType;
			ParameterizedType toPt = (ParameterizedType) types[0];
			Type[] fromPtTypes = fromPt.getActualTypeArguments();
			Type[] toPtTypes = toPt.getActualTypeArguments();
			return fromPtTypes != null && toPtTypes != null && fromPtTypes.length == toPtTypes.length
					&& fromPtTypes.length == 2 && fromPtTypes[0].equals(toPtTypes[0])
					&& fromPtTypes[1].equals(toPtTypes[1]);
		} catch (NoSuchMethodException e) {
			return false;
		}
	}

	private void checkEnumConvert(String beanClazzName, String fileName, Class fieldType) {
		if (!fieldType.equals(int.class) && !fieldType.equals(short.class) && !fieldType.equals(byte.class) &&
				!fieldType.equals(Integer.class) && !fieldType.equals(Short.class) && !fieldType.equals(Byte.class)) {
			throw new RuntimeException(beanClazzName + "." + fileName + " only support int short byte Integer Short Byte");
		}
	}

	private boolean hasSetterMethod(Class<?> clazz, String methodName, Field field) {
		try {
			clazz.getDeclaredMethod(methodName, field.getType());
			return true;
		} catch (NoSuchMethodException e) {
			return false;
		}
	}

	private boolean hasIsSetMethod(Class<?> clazz, String methodName) {
		try {
			clazz.getDeclaredMethod(methodName);
			return true;
		} catch (NoSuchMethodException e) {
			return false;
		}
	}

	private boolean hasGetterMethod(Class<?> clazz, String methodName) {
		try {
			clazz.getDeclaredMethod(methodName);
			return true;
		} catch (NoSuchMethodException e) {
			return false;
		}
	}

	/**
	 * 生成setter/getter方法名称，按照头字母大写规则进行生成，如果第二个字母为大写，那么首字母还是小写
	 */
	private String createSetterOrGetterMethodString(String prefix, String fieldName) {
		return MethodUtil.createSetterOrGetterMethodString(prefix, fieldName);
	}

	private String getIsSetterMethodName(String fieldName) {
		return "isSet" + getUpper1Name(fieldName);
	}

	private String getUpper1Name(String fieldName) {
		String first = fieldName.substring(0, 1);
		return first.toUpperCase() + fieldName.substring(1);
	}

	private String createGetterMethodName(Field field) {
		Class toFieldType = field.getType();
		if (toFieldType.equals(boolean.class)) {
			return createSetterOrGetterMethodString("is", field.getName());
		}
		return createSetterOrGetterMethodString("get", field.getName());
	}

	private boolean hasFindByValueInEnum(Class<?> enumClazz) {
		try {
			enumClazz.getDeclaredMethod("findByValue", int.class);
			return true;
		} catch (NoSuchMethodException e) {
			return false;
		}
	}

}